use super::{
    base::{VBase, VMap},
    client::Client,
    err, err_value, json, throw_err, ECode, MapSv, RNull, Value, VecV, SS,
};
use std::collections::{HashMap, HashSet};
use tracing::{instrument, warn};

pub trait TBase {
    fn get_mt_type(&self, mt: &str) -> Result<String, Value> {
        let ret = match mt {
            "G" => "stell",
            "T" => "cera",
            "S" => "seal",
            "C" => "cage",
            _ => {
                throw_err!(ECode::Para, "mt type error");
            }
        };

        Ok(ret.to_string())
    }

    fn get_box_tp(&self, data: &MapSv) -> Option<String> {
        let v = data.k_obj("attr")?.k_str_empty_trim("tp")?;
        Some(v.to_string())
    }

    fn get_box_task(&self, data: &MapSv) -> Option<String> {
        let v = data.k_obj("attr")?.k_str("task")?;
        Some(v.to_string())
    }

    fn str_or_empty(&self, data: &MapSv, k: &str) -> String {
        match data.k_str(k) {
            Some(v) => v.to_string(),
            None => "".to_string(),
        }
    }

    fn get_side_type(&self, mt: &str) -> Result<String, Value> {
        let ret = match mt {
            "hand" => "sidehand",
            "auto" => "sideauto",
            _ => {
                throw_err!(ECode::Para, "mt type error");
            }
        };

        Ok(ret.to_string())
    }

    fn collect_inner(&self, p: &MapSv, ret: &mut MapSv) -> Option<()> {
        let psn = p.k_str("insn")?;
        let india = p.k_f64("n_india")?;
        let indt = p.k_f64("n_indt")?;
        let inwidth = p.k_f64("n_inwidth")?;

        ret.insert(
            psn.to_string(),
            json!({
                "india": india,
                "indt": indt,
                "inwidth": inwidth,
            }),
        );

        Some(())
    }

    fn collect_outer(&self, p: &MapSv, ret: &mut MapSv) -> Option<()> {
        let psn = p.k_str("sn")?;
        let outdia = p.k_f64("n_outdia")?;
        let outdt = p.k_f64("n_outdt")?;
        let outwidth = p.k_f64("n_outwidth")?;

        ret.insert(
            psn.to_string(),
            json!({
                "outdia": outdia,
                "outdt": outdt,
                "outwidth": outwidth,
            }),
        );

        Some(())
    }

    /*  in: mm
     *  calc: um
     *
     * v > 0, round
     * v < 0, >= 0.5 up, < 0.5 down
     */
    fn round_dia(&self, v: f64) -> f64 {
        let v = v * 1000.0;

        let tmp = (v * 10.0).round() / 10.0;

        if tmp >= 0.0 {
            return tmp.round() / 1000.0;
        }

        let diff = (tmp.fract() + 0.5).abs();
        if diff <= 1e-10 {
            return tmp.trunc() / 1000.0;
        }

        tmp.round() / 1000.0
    }

    fn get_out_val(&self, out_psn: &MapSv) -> Option<(f64, f64, f64, String, bool, String)> {
        let (outdia, outdt, outwidth, gc, boxout) = self.get_out(out_psn).ok()?;

        let tsn = match out_psn.k_str_empty_trim("tsn") {
            Some(v) => v,
            None => "",
        };

        Some((outdia, outdt, outwidth, tsn.to_string(), gc, boxout))
    }

    fn get_in_val(&self, in_psn: &MapSv) -> Option<(f64, f64, f64, String, bool, String)> {
        let (india, indt, inwidth, gc, boxin) = self.get_in(in_psn).ok()?;

        let tsn = match in_psn.k_str_empty_trim("tsn") {
            Some(v) => v,
            None => "",
        };

        Some((india, indt, inwidth, tsn.to_string(), gc, boxin))
    }

    fn get_out(&self, out_psn: &MapSv) -> Result<(f64, f64, f64, bool, String), Value> {
        let outdia = out_psn.ck_f64("outdia")?; // D
        let outdt = out_psn.ck_f64("outdt")?; // De
        let outwidth = out_psn.ck_f64("outwidth")?; // C
        let gc = match out_psn.k_bool("gc") {
            Some(v) => v,
            None => false,
        };
        let box_sn = match out_psn.k_str_empty_trim("boxout") {
            Some(v) => v,
            None => "",
        };

        Ok((outdia, outdt, outwidth, gc, box_sn.to_string()))
    }

    fn get_in(&self, in_psn: &MapSv) -> Result<(f64, f64, f64, bool, String), Value> {
        let india = in_psn.ck_f64("india")?; // d
        let indt = in_psn.ck_f64("indt")?; // di
        let inwidth = in_psn.ck_f64("inwidth")?; // B
        let gc = match in_psn.k_bool("gc") {
            Some(v) => v,
            None => false,
        };
        let boxin = match in_psn.k_str_empty_trim("boxin") {
            Some(v) => v,
            None => "",
        };

        Ok((india, indt, inwidth, gc, boxin.to_string()))
    }

    fn get_val(&self, val: &MapSv) -> Option<(f64, f64, f64)> {
        let dia = val.k_f64("dia")?;
        let dt = val.k_f64("dt")?;
        let width = val.k_f64("width")?;

        Some((dia, dt, width))
    }

    fn get_range(&self, data: &MapSv, s: &str, e: &str) -> (bool, f64, f64) {
        let min = match data.k_f64(s) {
            Some(v) => v,
            None => {
                return (false, 0.0, 0.0);
            }
        };

        let max = match data.k_f64(e) {
            Some(v) => v,
            None => {
                return (false, 0.0, 0.0);
            }
        };

        if min > max {
            (false, 0.0, 0.0)
        } else {
            (true, min / 1000.0, max / 1000.0)
        }
    }

    /* tsn: {
     *    psn: {...}
     * }
     */
    fn insert_pair_data(&self, cont: &mut MapSv, tsn: &str, psn: &str, val: Value) {
        match cont.k_obj_mut(tsn) {
            Some(v) => {
                v.insert(psn.to_string(), val);
            }
            None => {
                cont.insert(tsn.to_string(), json!({ psn: val }));
            }
        }
    }

    /* tsn: {
     *    psn: {
     *       "inwidth": xx,
     *       ...
     *    }
     * }
     */
    fn insert_data(&self, cont: &mut MapSv, tab: &str, tsn: &str, psn: &str, val: Value) {
        match cont.k_obj_mut(tsn) {
            Some(v) => match v.k_obj_mut(psn) {
                Some(pv) => {
                    pv.insert(tab.to_string(), val);
                }
                None => {
                    v.insert(psn.to_string(), json!({ tab: val }));
                }
            },
            None => {
                cont.insert(
                    tsn.to_string(),
                    json!({
                        psn: {
                            tab: val,
                        }
                    }),
                );
            }
        }
    }

    fn is_range(&self, d: f64, min: f64, max: f64) -> bool {
        let diff = 100000.0;

        let i_d = (d * diff) as i64;
        let i_min = (min * diff) as i64;
        let i_max = (max * diff) as i64;

        i_d >= i_min && i_d <= i_max
    }

    fn is_le(&self, d: f64, v: f64) -> bool {
        let diff = 100000.0;

        let i_d = (d * diff) as i64;
        let i_v = (v * diff) as i64;

        i_d <= i_v
    }

    fn is_eq(&self, d: f64, v: f64) -> bool {
        let diff = 100000.0;

        let i_d = (d * diff) as i64;
        let i_v = (v * diff) as i64;

        i_d == i_v
    }

    fn to_m(&self, v: f64) -> String {
        to_m(v)
    }
}

pub fn to_m(v: f64) -> String {
    format!("{:.3}", v * 1000.0)
}

/*  outer
 *    ["tsn1", ...]
 *
 *  outer_<tsn1>
 *
 *    psn: {
 *       "inwidth": xx,
 *       ...
 *    }
 */
pub async fn save_data(line: &str, fname: &str, cont: &MapSv, client: &Client) -> RNull {
    if cont.is_empty() {
        return Ok(());
    }

    let mut out = Vec::new();

    for (k, v) in cont.iter() {
        out.push(k.to_string());

        let mut part_data = Vec::new();
        let v = v.ckobj()?;

        for (pk, pv) in v.iter() {
            part_data.push((pk, pv));
        }

        for d in part_data.chunks(100) {
            let mut tmp = HashMap::new();
            for s in d.iter() {
                tmp.insert(s.0, s.1);
            }

            let cmd = json!({
                "line": line,
                "v": tmp,
            });

            let _ = client
                .cfgdb(&cmd, &format!("file/modify/{fname}_{k}"))
                .await;
        }
    }

    let cmd = json!({ "line": line });

    if let Ok(v) = client.cfgdb(&cmd, &format!("field/list/{fname}")).await {
        if let Ok(v) = v.ck_array("data") {
            for d in v.iter() {
                let d = d.ckstr()?;
                out.push(d.to_string());
            }
        }
    }

    out.sort_unstable();
    out.dedup();

    let cmd = json!({
        "line": line,
        "v": out
    });

    let _ = client.cfgdb(&cmd, &format!("field/md/{fname}")).await;

    Ok(())
}

pub async fn get_cfg(fname: &str, line: &str, cfg: &mut SS, client: &Client) -> RNull {
    let cmd = json!({ "line": line });

    let body = client.cfgdb(&cmd, &format!("field/list/{fname}")).await?;
    let body = body.ck_obj("data")?;

    for (k, v) in body.iter() {
        let v = v.ckstr()?;
        cfg.insert(k.to_string(), v.to_string());
    }

    Ok(())
}

pub async fn save_cfg(fname: &str, line: &str, cfg: &SS, client: &Client) -> RNull {
    let cmd = json!({
        "line": line,
        "v": cfg,
    });

    client.cfgdb(&cmd, &format!("field/md/{fname}")).await?;

    Ok(())
}

#[instrument(skip_all, name = "load_ring")]
pub async fn load_ring(
    fname: &str,
    tsn: &str,
    line: &str,
    client: &Client,
    other: Option<&str>,
) -> Result<MapSv, Value> {
    let mut ret = MapSv::new();

    let cmd = json!({ "line": line });

    if let Ok(body) = client
        .cfgdb(&cmd, &format!("file/read/{fname}_{tsn}"))
        .await
    {
        let body = body.ck_obj("data")?;
        for (k, v) in body.iter() {
            ret.insert(k.to_string(), v.clone());
        }
    }

    if let Some(tsn_other) = other {
        warn!("load ring: load other tsn");

        if let Ok(body_other) = client
            .cfgdb(&cmd, &format!("file/read/{fname}_{tsn_other}"))
            .await
        {
            let body_other = body_other.ck_obj("data")?;

            for (k, v) in body_other.iter() {
                let mut v = v.ckobj()?.clone();
                v.insert("tsn".to_string(), json!(tsn_other));

                ret.insert(k.to_string(), json!(v));
            }
        }
    }

    Ok(ret)
}

pub async fn update_side(
    line: &str,
    side_mod: &str,
    sn: &str,
    num: i64,
    add: bool,
    client: &Client,
) -> Result<bool, Value> {
    let cmd = json!({
        "line": line,
        "k": sn,
    });

    if let Ok(body) = client.cfgdb(&cmd, &format!("{side_mod}/get")).await {
        let body = body.ck_obj("data")?;

        let stock_num = match body.k_i64("num") {
            Some(v) => v,
            None => 0,
        };

        let left_num = if add {
            num + stock_num
        } else {
            stock_num - num
        };

        if left_num > 0 {
            let cmd = json!({
                "line": line,
                "v": {
                    "sn": sn,
                    "num": left_num,
                }
            });

            let _ = client.cfgdb(&cmd, &format!("{side_mod}/mditem")).await?;
        } else {
            let cmd = json!({
                "line": line,
                "k": sn,
            });

            let _ = client.cfgdb(&cmd, &format!("{side_mod}/rm")).await?;
        }

        Ok(true)
    } else {
        Ok(false)
    }
}

pub async fn log_pack(line: &str, tab: &str, psn: &str, tsn: &str, client: &Client) -> RNull {
    let mut val = MapSv::new();
    val.insert("tsn".to_string(), json!(tsn));

    log_stock(line, tab, psn, "auto", &mut val, None, client, "log").await
}

pub async fn log_stock(
    line: &str,
    tab: &str,
    psn: &str,
    wsn: &str,
    val: &MapSv,
    kv: Option<Value>,
    client: &Client,
    db: &str,
) -> RNull {
    let mut tmp = HashMap::new();

    for (k, v) in val.iter() {
        let k = k.as_str();

        match k {
            "usn" | "psn" | "wsn" | "line" => {
                continue;
            }
            _ => {
                if let Some(t) = v.kstr() {
                    tmp.insert(k, t.to_string());
                } else if let Some(t) = v.ki64() {
                    tmp.insert(k, t.to_string());
                } else if let Some(t) = v.kf64() {
                    tmp.insert(k, t.to_string());
                } else if let Some(t) = v.kbool() {
                    tmp.insert(k, t.to_string());
                }
            }
        }
    }

    let data = match kv {
        Some(v) => json!({
            "psn": psn,
            "wsn": wsn,
            "k": v,
            "v": tmp
        }),
        None => json!({
            "psn": psn,
            "wsn": wsn,
            "v": tmp
        }),
    };

    let db = if db.is_empty() { "realtime" } else { db };

    let cmd = json!({
       "tab": tab,
       "db": db,
       "line": line,
       "que": true,
       "data": [data]
    });

    let _ = client.timedb(&cmd, "chunk").await?;

    Ok(())
}

pub async fn update_stock(
    line: &str,
    opt: &Value,
    sn: &str,
    mt_mod: &str,
    num: i64,
    client: &Client,
) -> RNull {
    let cmd = json!({
        "line": line,
        "k": sn,
    });

    let body = client.cfgdb(&cmd, &format!("{mt_mod}/get")).await?;
    let stock_data = body.ck_obj("data")?;

    let stock_num = stock_data.ck_i64("num")?;
    let bnum = match stock_data.k_i64("bnum") {
        Some(v) => v,
        None => 0,
    };

    let num_left = stock_num - num;
    let mut bnum_left = bnum - num;
    if bnum_left < 0 {
        bnum_left = 0;
    }

    if num_left <= 0 {
        let cmd = json!({
            "line": line,
            "k": sn,
        });

        let _ = client.cfgdb(&cmd, &format!("{mt_mod}/rm")).await?;
    } else {
        let cmd = json!({
            "line": line,
            "v": {
                "sn": sn,
                "num": num_left,
                "bnum": bnum_left,
            }
        });

        let _ = client.cfgdb(&cmd, &format!("{mt_mod}/mditem")).await?;
    }

    let mut val = stock_data.clone();

    let opt = opt.ckobj()?;

    let tab = opt.ck_str("tab")?;
    let psn = opt.ck_str("psn")?;
    let wsn = opt.ck_str("wsn")?;
    let data = opt.ck_obj("data")?;

    for (k, v) in data.iter() {
        val.insert(k.to_string(), v.clone());
    }

    let _ = log_stock(line, tab, psn, wsn, &mut val, None, client, "log").await;

    Ok(())
}

pub async fn box_psn(
    line: &str,
    box_sn: &str,
    client: &Client,
) -> Result<(String, HashSet<String>), Value> {
    let mut tp = "".to_string();
    let mut psn_set = HashSet::new();

    let cmd = json!({ "line": line });

    let body = client
        .cfgdb(&cmd, &format!("file/list/box_{box_sn}"))
        .await?;
    let body = body.ck_array("data")?;

    for d in body.iter() {
        let d = d.ckobj()?;

        let sn = d.ck_str("sn")?;
        if sn == "attr" {
            tp = d.ck_str("tp")?.to_string();
            continue;
        }

        psn_set.insert(sn.to_string());
    }

    Ok((tp, psn_set))
}

pub async fn load_box(line: &str, outer: &mut HashSet<String>, md: &str, client: &Client) -> RNull {
    let body = load_ready(md, Some("box"), None, line, client).await?;

    for (_, v) in body.iter() {
        let v = v.ckobj()?;

        for (psn, _) in v.iter() {
            if psn == "attr" {
                continue;
            }

            outer.insert(psn.to_string());
        }
    }

    Ok(())
}

#[instrument(skip_all, name = "load_ready")]
pub async fn load_ready(
    fname: &str,
    sub: Option<&str>,
    filter: Option<&VecV>,
    line: &str,
    client: &Client,
) -> Result<MapSv, Value> {
    let cmd = json!({ "line": line });

    let cont = client.cfgdb(&cmd, &format!("field/list/{fname}")).await?;
    let cont = cont.ck_array("data")?;

    let mut ret = MapSv::new();

    let sub_path = match sub {
        Some(k) => format!("file/read/{k}_"),
        None => format!("file/read/{fname}_"),
    };

    let mut fil = HashSet::new();

    if let Some(body) = filter {
        for d in body.iter() {
            let d = d.ckstr()?;
            fil.insert(d.to_string());
        }
    }

    for k in cont.iter() {
        let k = k.ckstr()?;

        if !fil.is_empty() {
            if !fil.contains(k) {
                continue;
            }
        }

        let body = match client.cfgdb(&cmd, &format!("{sub_path}{k}")).await {
            Ok(v) => v,
            Err(e) => {
                warn!("load_read error: {:?}", e);
                continue;
            }
        };

        let body = body.ck_val("data")?;

        ret.insert(k.to_string(), body.clone());
    }

    Ok(ret)
}

pub async fn try_rm_task(line: &str, task_sn: &str, client: &Client) -> RNull {
    let cmd = json!({
        "line": line,
        "k": task_sn,
    });

    if let Err(_) = client.cfgdb(&cmd, "psn/list").await {
        let _ = client.cfgdb(&cmd, "task/rm").await?;
    }

    Ok(())
}
